﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Remoting.Messaging;
using System.Text;
using System.Threading;
using SmartMathLibrary.Space.QueryEngine;

namespace SmartMathLibrary.Space
{
    /// <summary>
    /// This method defines the ItemAddedEventHandler.
    /// </summary>
    public delegate void ItemAddedEventHandler(uint id);

    /// <summary>
    /// This method defines the ItemDeletedEventHandler.
    /// </summary>
    public delegate void ItemDeletedEventHandler(uint id);

    /// <summary>
    /// This method defines the ItemUpdatedEventHandler.
    /// </summary>
    public delegate void ItemUpdatedEventHandler(uint id);

    /// <summary>
    /// Defines the request mode for a space object request.
    /// </summary>
    public enum RequestMode
    {
        /// <summary>
        /// Defines that the request for a space object will not wait until the specified 
        /// object is not locked any more. This is the default parameter.
        /// </summary>
        NoWait = 0,
        /// <summary>
        /// Defines that the request for a space object will ask for it until the specified 
        /// object is not locked any more.
        /// </summary>
        Wait = 1
    }

    /// <summary>
    /// This class represents a space container. 
    /// </summary>
    public class SpaceContainer : MarshalByRefObject
    {
        /// <summary>
        /// This field holds the space items.
        /// </summary>
        private List<SpaceItem> items;

        /// <summary>
        /// This field holds the space item keys, which reference to the list indices.
        /// </summary>
        private readonly Dictionary<uint, int> keyDictionary;

        private readonly Dictionary<uint, uint> autoRemoveList;
        private readonly Dictionary<uint, uint> autoRemoveCounterList;

        private SpaceGarbageCollector garbageCollector;

        internal event ItemAddedEventHandler ItemAdded;
        internal event ItemDeletedEventHandler ItemDeleted;
        internal event ItemUpdatedEventHandler ItemUpdated;

        /// <summary>
        /// Initializes a new instance of the <see cref="SpaceContainer"/> class.
        /// </summary>
        public SpaceContainer()
        {
            this.items = new List<SpaceItem>();
            this.keyDictionary = new Dictionary<uint, int>();
            this.autoRemoveList = new Dictionary<uint, uint>();
            this.autoRemoveCounterList = new Dictionary<uint, uint>();
            this.garbageCollector = new SpaceGarbageCollector(  this , ref this.autoRemoveList,ref this.autoRemoveCounterList);

            Thread collectorThread = new Thread(new ThreadStart(this.CollectUnusedItems));

            collectorThread.Start();
        }

        private void CollectUnusedItems()
        {
            while (true)
            {
                //Collect items and wait one minute
                Thread.Sleep(1000 * 60);
                this.garbageCollector.CollectItems();
            }
        }

        /// <summary>
        /// Checks if the space object with the specified ID exists.
        /// </summary>
        /// <param name="id">The specified ID of the space object.</param>
        /// <returns>True, if the space object exists otherwise, false.</returns> 
        public bool Exists(uint id)
        {
            return this.keyDictionary.ContainsKey(id);
        }

        /// <summary>
        /// Checks if the space object with the specified ID is locked.
        /// </summary>
        /// <param name="id">The specified ID of the space object.</param>
        /// <returns>True, if the space object is locked otherwise, false.</returns>
        public bool IsLocked(uint id)
        {
            return this.items[this.keyDictionary[id]].Locked;
        }

        /// <summary>
        /// Adds the specified item to the object space.
        /// </summary>
        /// <param name="item">The item to add.</param>
        public void Add(SpaceItem item)
        {
            Thread.BeginCriticalRegion();

            if (this.keyDictionary.ContainsKey(item.Id))
            {
                throw new SpaceIDAlreadyExistsException("The specified SpaceID already exists.");
            }

            this.keyDictionary.Add(item.Id, this.items.Count);
            this.items.Add(item);
            this.OnItemAdded(item.Id);
            Thread.EndCriticalRegion();
        }

        /// <summary>
        /// Adds the specified items to the object space.
        /// </summary>
        /// <param name="itemlist">The items to add.</param>
        public void Add(SpaceItem[] itemlist)
        {
            for (int i = 0; i < itemlist.Length; i++)
            {
                this.Add(itemlist[i]);
            }
        }

        public void Add(SpaceItem item,uint removeTime)
        {
            Thread.BeginCriticalRegion();
            this.Add(item);
            
            if (removeTime != uint.MaxValue)
            {
                this.autoRemoveList.Add(item.Id, removeTime);
                this.autoRemoveCounterList.Add(item.Id, 0);
            }

            Thread.EndCriticalRegion();
        }

        public void Add(uint id, ISpaceEntry value, uint removeTime)
        {
            Thread.BeginCriticalRegion();
            this.Add(id,value);
            
            if (removeTime != uint.MaxValue)
            {
                this.autoRemoveList.Add(id, removeTime);
                this.autoRemoveCounterList.Add(id, 0);
            }
            
            Thread.EndCriticalRegion();
        }

        /// <summary>
        /// Adds the specified item to the object space.
        /// </summary>
        /// <param name="id">The ID of the space object.</param>
        /// <param name="value">The item, which should be added to the object space.</param>
        public void Add(uint id, ISpaceEntry value)
        {
            this.Add(new SpaceItem(id, value));
        }

        /// <summary>
        /// Requests the space object, which owns the specified space ID. If the space object is locked or 
        /// does not exist, the method will throw an exception.
        /// </summary>
        /// <param name="id">The specified space ID.</param>
        /// <returns>The space object with the specified ID.</returns>
        public SpaceItem RequestSpaceObject(uint id)
        {
            Thread.BeginCriticalRegion();

            if (!this.Exists(id))
            {
                throw new SpaceObjectNotFoundException("The requested object does not exist.");
            }

            if (this.IsLocked(id))
            {
                throw new SpaceObjectLockException("The requested object owns lock status.");
            }

            this.items[this.keyDictionary[id]].Locked = true;
            Thread.EndCriticalRegion();

            return this.items[this.keyDictionary[id]];
        }

        /// <summary>
        /// Requests the space object, which owns the specified space ID. If the space object is locked or
        /// does not exist, the method will throw an exception.
        /// </summary>
        /// <param name="id">The specified space ID.</param>
        /// <param name="mode">The request mode.</param>
        /// <returns>The space object with the specified ID.</returns>
        public SpaceItem RequestSpaceObject(uint id, RequestMode mode)
        {
            if (mode == RequestMode.NoWait)
            {
                return this.RequestSpaceObject(id);
            }

            do
            {
                // Active wait
                Thread.Sleep(10);
            } while (this.IsLocked(id));

            return this.RequestSpaceObject(id);
        }

        /// <summary>
        /// Requests the space object, which owns the specified space ID. If the space object is locked or
        /// does not exist, the method will throw an exception.
        /// </summary>
        /// <param name="id">The specified space ID.</param>
        /// <param name="milliseconds">The maximum time to wait for the space object in milliseconds.</param>
        /// <returns>The space object with the specified ID.</returns>
        public SpaceItem RequestSpaceObject(uint id, int milliseconds)
        {
            if (milliseconds <= 0)
            {
                return this.RequestSpaceObject(id);
            }

            int counter = 0;

            do
            {
                // Active wait
                Thread.Sleep(1);
                counter++;
            } while (this.IsLocked(id) && (counter < milliseconds));

            return this.RequestSpaceObject(id);
        }

        /// <summary> 
        /// Requests a copy of a space object. This method will always return a copy of the space object. 
        /// It does not matter if the space object owns lock status.
        /// </summary>
        /// <param name="id">The specified ID of the space object.</param>
        /// <returns>The space object with the specified space object ID.</returns>
        public SpaceItem RequestSpaceObjectCopy(uint id)
        {
            if (!this.Exists(id))
            {
                throw new SpaceObjectNotFoundException("The requested object does not exist.");
            }

            return (SpaceItem) this.items[this.keyDictionary[id]].Clone();
        }

        /// <summary>
        /// Unlocks the space object, which owns the specified space ID. If the space item´s value should 
        /// be updated use the Update() method.
        /// </summary>
        /// <param name="id">The specified ID, which should be unlocked.</param>
        public void Pushback(uint id)
        {
            if (this.autoRemoveList.ContainsKey(id))
            {
                this.autoRemoveCounterList[id] = 0;
            }

            if (!this.Exists(id))
            {
                throw new SpaceObjectNotFoundException("The requested object does not exist.");
            }

            Thread.BeginCriticalRegion();
            this.items[this.keyDictionary[id]].Locked = false;
            Thread.EndCriticalRegion();
        }

        /// <summary>
        /// Unlocks the specified space object. If the space item´s value should be updated use the Update() method.
        /// </summary>
        /// <param name="item">The specified space item, which should be unlocked.</param>
        public void Pushback(SpaceItem item)
        {
            this.Pushback(item.Id);
        }

        /// <summary>
        /// Updates the value of a space object with the specified ID. This method will also unlock 
        /// the specified space object. If the space object to update owns no lock status or does not exist 
        /// this method will throw an exception.
        /// </summary>
        /// <param name="id">The ID of the space object to update.</param>
        /// <param name="value">The update value.</param>
        public void Update(uint id, ISpaceEntry value)
        {
            if (this.autoRemoveList.ContainsKey(id))
            {
                this.autoRemoveCounterList[id] = 0;
            }

            if (!this.Exists(id))
            {
                throw new SpaceObjectNotFoundException("The space object does not exist.");
            }

            if (!this.IsLocked(id))
            {
                throw new SpaceObjectUpdateFailedException(
                    "The requested object does not owns lock status, so it cannot be updated.");
            }

            this.items[this.keyDictionary[id]].Value = value;
            this.Pushback(id);
            this.OnItemUpdated(id);
        }

        /// <summary>
        /// Updates the value of a space object with the specified ID. This method will also unlock
        /// the specified space object. If the space object to update owns no lock status or does not exist
        /// this method will throw an exception.
        /// </summary>
        /// <param name="item">The specified space object to update.</param>
        public void Update(SpaceItem item)
        {
            this.Update(item.Id, item.Value);
        }

        /// <summary>
        /// Deletes the space object, which owns the specified space ID. If the space object owns lock 
        /// status or does not exist, this method will throw an exception.
        /// </summary>
        /// <param name="id">The specified ID of the space object to delete.</param>
        public void Delete(uint id)
        {
            Thread.BeginCriticalRegion();

            if (!this.Exists(id))
            {
                throw new SpaceObjectNotFoundException("The requested object does not exist.");
            }

            if (this.IsLocked(id))
            {
                throw new SpaceObjectLockException("The object owns lock status and cannot be deleted.");
            }

            if (this.autoRemoveList.ContainsKey(id))
            {
                this.autoRemoveList.Remove(id);
                this.autoRemoveCounterList.Remove(id);
            }

            this.items.RemoveAt(this.keyDictionary[id]);
            this.keyDictionary.Remove(id);
            this.OnItemDeleted(id);
            Thread.EndCriticalRegion();
        }

        /// <summary>
        /// Deletes the space object, which owns the specified space ID. If the space object owns lock 
        /// status or does not exist, this method will throw an exception.
        /// </summary>
        /// <param name="item">The specified space object to delete.</param>
        public void Delete(SpaceItem item)
        {
            this.Delete(item.Id);
        }

        /// <summary>
        /// Executes a specified query.
        /// </summary>
        /// <param name="query">The query to execute.</param>
        /// <returns>The result of the specified query.</returns>
        public uint[] ExecuteQuery(AbstractSpaceQuery query)
        {
             SpaceQueryEngine engine = new  SpaceQueryEngine(ref this.items);

            return engine.ExecuteQuery(query);
        }

        /// <summary>
        /// Searches in the object space for objects by using a template. The fields, which 
        /// the search should include have to be attributes, which are marked as SpaceAttribute. 
        /// </summary>
        /// <param name="template">The template, which defines the search parameters.</param>
        /// <returns>A list of space ID´s, which comply the template criterias.</returns>
        public uint[] SearchByTemplate(ISpaceEntry template)
        {
            List<uint> result = new List<uint>();
            ValueAnalyzer templateAnalyzer = new ValueAnalyzer(template);
            Field[] templateFields = templateAnalyzer.AnalyzeValues();

            for (int i = 0; i < this.items.Count; i++)
            {
                if (!this.items[i].Locked)
                {
                    bool addID = true;
                    ValueAnalyzer analyzer = new ValueAnalyzer(this.items[i].Value);
                    Field[] fields = analyzer.AnalyzeValues();

                    if (templateFields.Length != fields.Length)
                    {
                        continue;
                    }

                    for (int j = 0; j < fields.Length; j++)
                    {
                        if (templateFields[j].Value != null)
                        {
                            if ((fields[j].Type.IsArray) && (templateFields[j].Type.IsArray))
                            {
                                if (fields[j].Value.GetType() == typeof(double[]))
                              {
                                  double[] fieldsValue = (double[])fields[j].Value;
                                  double[] templateFieldsValue = (double[])templateFields[j].Value;

                                  if (fieldsValue.Length == templateFieldsValue.Length)
                                  {
                                      for (int t=0;t < fieldsValue.Length;t++ )
                                      {
                                          if (fieldsValue[t].GetHashCode() != templateFieldsValue[t].GetHashCode())
                                          {
                                              addID = false;
                                              break;
                                          }
                                      }
                                  }
                                  else
                                  {
                                      addID = false;
                                      break;
                                  }
                              }
                                else if (fields[j].Value.GetType() == typeof(int[]))
                                {
                                    int[] fieldsValue = (int[])fields[j].Value;
                                    int[] templateFieldsValue = (int[])templateFields[j].Value;

                                    if (fieldsValue.Length == templateFieldsValue.Length)
                                    {
                                        for (int t = 0; t < fieldsValue.Length; t++)
                                        {
                                            if (fieldsValue[t].GetHashCode() != templateFieldsValue[t].GetHashCode())
                                            {
                                                addID = false;
                                                break;
                                            }
                                        }
                                    }
                                    else
                                    {
                                        addID = false;
                                        break;
                                    }
                                }
                                else if (fields[j].Value.GetType() == typeof(string[]))
                                {
                                    string[] fieldsValue = (string[])fields[j].Value;
                                    string[] templateFieldsValue = (string[])templateFields[j].Value;

                                    if (fieldsValue.Length == templateFieldsValue.Length)
                                    {
                                        for (int t = 0; t < fieldsValue.Length; t++)
                                        {
                                            if (fieldsValue[t].GetHashCode() != templateFieldsValue[t].GetHashCode())
                                            {
                                                addID = false;
                                                break;
                                            }
                                        }
                                    }
                                    else
                                    {
                                        addID = false;
                                        break;
                                    }
                                }
                                else if (fields[j].Value.GetType() == typeof(object[]))
                                {
                                    object[] fieldsValue = (object[])fields[j].Value;
                                    object[] templateFieldsValue = (object[])templateFields[j].Value;

                                    if (fieldsValue.Length == templateFieldsValue.Length)
                                    {
                                        for (int t = 0; t < fieldsValue.Length; t++)
                                        {
                                            if (fieldsValue[t].GetHashCode() != templateFieldsValue[t].GetHashCode())
                                            {
                                                addID = false;
                                                break;
                                            }
                                        }
                                    }
                                    else
                                    {
                                        addID = false;
                                        break;
                                    }
                                }
                                else if (fields[j].Value.GetType() == typeof(double[,]))
                                {
                                    double[,] fieldsValue = (double[,])fields[j].Value;
                                    double[,] templateFieldsValue = (double[,])templateFields[j].Value;

                                    if (fieldsValue.Length == templateFieldsValue.Length)
                                    {
                                        for (int s = 0; s < fieldsValue.GetLength(0); s++)
                                        {
                                            for (int t = 0; t < fieldsValue.GetLength(1); t++)
                                            {
                                                if (fieldsValue[s,t].GetHashCode() != templateFieldsValue[s,t].GetHashCode())
                                                {
                                                    addID = false;
                                                    break;
                                                }
                                            }
                                        }
                                    }
                                    else
                                    {
                                        addID = false;
                                        break;
                                    }
                                }
                                else if (fields[j].Value.GetType() == typeof(int[,]))
                                {
                                    int[,] fieldsValue = (int[,])fields[j].Value;
                                    int[,] templateFieldsValue = (int[,])templateFields[j].Value;

                                    if (fieldsValue.Length == templateFieldsValue.Length)
                                    {
                                        for (int s = 0; s < fieldsValue.GetLength(0); s++)
                                        {
                                            for (int t = 0; t < fieldsValue.GetLength(1); t++)
                                            {
                                                if (fieldsValue[s, t].GetHashCode() != templateFieldsValue[s, t].GetHashCode())
                                                {
                                                    addID = false;
                                                    break;
                                                }
                                            }
                                        }
                                    }
                                    else
                                    {
                                        addID = false;
                                        break;
                                    }
                                }
                                else if (fields[j].Value.GetType() == typeof(string[,]))
                                {
                                    string[,] fieldsValue = (string[,])fields[j].Value;
                                    string[,] templateFieldsValue = (string[,])templateFields[j].Value;

                                    if (fieldsValue.Length == templateFieldsValue.Length)
                                    {
                                        for (int s = 0; s < fieldsValue.GetLength(0); s++)
                                        {
                                            for (int t = 0; t < fieldsValue.GetLength(1); t++)
                                            {
                                                if (fieldsValue[s, t].GetHashCode() != templateFieldsValue[s, t].GetHashCode())
                                                {
                                                    addID = false;
                                                    break;
                                                }
                                            }
                                        }
                                    }
                                    else
                                    {
                                        addID = false;
                                        break;
                                    }
                                }
                                else if (fields[j].Value.GetType() == typeof(object[,]))
                                {
                                    object[,] fieldsValue = (object[,])fields[j].Value;
                                    object[,] templateFieldsValue = (object[,])templateFields[j].Value;

                                    if (fieldsValue.Length == templateFieldsValue.Length)
                                    {
                                        for (int s = 0; s < fieldsValue.GetLength(0); s++)
                                        {
                                            for (int t = 0; t < fieldsValue.GetLength(1); t++)
                                            {
                                                if (fieldsValue[s, t].GetHashCode() != templateFieldsValue[s, t].GetHashCode())
                                                {
                                                    addID = false;
                                                    break;
                                                }
                                            }
                                        }
                                    }
                                    else
                                    {
                                        addID = false;
                                        break;
                                    }
                                }
                                else
                                {
                                    if ( fields[j].Value.GetType() != templateFields[j].GetType())
                                    {
                                        addID = false;
                                        break;
                                    }

                                    continue;
                                }
                            }
                            else if ((fields[j].Value.GetHashCode()) != templateFields[j].Value.GetHashCode())
                            {
                                addID = false;
                                break;
                            }
                        }
                    }

                    if (addID)
                    {
                        result.Add(this.items[i].Id);
                    }
                }
            }

            return result.ToArray();
        }

        /// <summary>
        /// Requests the space object, which owns the specified space ID. If the space object is locked or
        /// does not exist, the method will throw an exception. After the item requested, the space object will be 
        /// delete.
        /// </summary>
        /// <param name="id">The specified space ID.</param>
        /// <returns>The space object with the specified ID.</returns>
        public SpaceItem RequestAndDeleteSpaceObject(uint id)
        {
            Thread.BeginCriticalRegion();

            SpaceItem result = this.RequestSpaceObject(id);

            this.Pushback(id);
            this.Delete(id);
            Thread.EndCriticalRegion(); 

            return result;
        }

        /// <summary>
        /// Returns the next free space object ID of the specified object space.
        /// </summary>
        /// <returns>The next free space object ID of the specified object space.</returns>
        public uint GetFreeID()
        {
            Thread.BeginCriticalRegion();

            uint freeId = (uint) this.items.Count;

            while (this.Exists(freeId))
            {
                freeId++;
            }

            Thread.EndCriticalRegion();

            return freeId;
        }

        [OneWay]
        private void OnItemAdded(uint id)
        {
            ItemAddedEventHandler handler = this.ItemAdded;

            if (handler != null)
            {
                handler(id);
            }
        }

        [OneWay]
        private void OnItemDeleted(uint id)
        {
            ItemDeletedEventHandler handler = this.ItemDeleted;

            if (handler != null)
            {
                handler(id);
            }
        }

        [OneWay]
        private void OnItemUpdated(uint id)
        {
            ItemUpdatedEventHandler handler = this.ItemUpdated;

            if (handler != null)
            {
                handler(id);
            }
        }
    }
}